// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包日志实现了一个简单的日志包。它定义了一个类型Logger 
// 以及格式化输出的方法。它还有一个预定义的“标准”
// 记录器，可通过辅助函数Print[f|ln]、Fatal[f|ln]和
// Panic[f|ln]访问，这比手动创建记录器更容易使用。
// 该记录器写入标准错误，并打印每个记录消息的日期和时间
// 。
// 每个日志消息都在单独的一行上输出：如果正在打印的消息未以换行结束，记录器将添加一行。
// 致命函数调用os。写入日志消息后退出（1）。
// Panic函数在写入日志消息后调用Panic。
package log

import (
	"fmt"
	"io"
	"os"
	"runtime"
	"sync"
	"sync/atomic"
	"time"
)

// 这些标志定义了日志记录器生成的每个日志条目的前缀文本。
// 位被或组合在一起以控制打印内容。
// 除Lmsgprefix标志外，没有任何
// 控制它们出现的顺序（此处列出的顺序）
// 或它们呈现的格式（如注释所述）。
// 仅当指定了Llongfile或Lshortfile 
// 时，前缀后面才跟一个冒号。例如，标志Ldate | Ltime（或LstdFlags）生成，标志Ldate | Ltime | Lmicroseconds | Llongfile生成，
// 2009/01/23 01:23:23.123123/a/b/c/d.go:23:message 
const (
	Ldate         = 1 << iota     // 本地时区的日期：2009/01/23 
	Ltime                         // 本地时区的时间：01:23:23 
	Lmicroseconds                 // 微秒分辨率：01:23:23.123123。时间到了。
	Llongfile                     // 完整文件名和行号：/a/b/c/d.go:23 
	Lshortfile                    // 最终文件名元素和行号：d.go:23。覆盖Llongfile 
	LUTC                          // 如果设置了Ldate或Ltime，则使用UTC而不是本地时区
	Lmsgprefix                    // 将“前缀”从行的开头移动到消息
	LstdFlags     = Ldate | Ltime // 标准记录器的初始值
)

// 记录器表示一个活动的日志对象，该对象生成
// 行输出到io。作家每个日志操作都对
// 编写器的写入方法进行一次调用。一个记录器可以从
// 多个goroutine同时使用；它保证序列化对写入程序的访问。
type Logger struct {
	mu        sync.Mutex // 确保原子写入；保护每行的以下字段
	prefix    string     // 前缀以标识记录器（但请参见Lmsgprefix）
	flag      int        // 属性
	out       io.Writer  // 输出目的地
	buf       []byte     // 用于累积文本以写入
	isDiscard int32      // 原子布尔值：是否输出==io。丢弃
}

// New创建一个新的记录器。out变量设置将写入日志数据的
// 目标。
// 如果提供了Lmsgprefix标志，则前缀出现在每个生成的日志行的开头，或者
// 出现在日志头之后。
// flag参数定义日志属性。
func New(out io.Writer, prefix string, flag int) *Logger {
	l := &Logger{out: out, prefix: prefix, flag: flag}
	if out == io.Discard {
		l.isDiscard = 1
	}
	return l
}

// SetOutput设置记录器的输出目标。
func (l *Logger) SetOutput(w io.Writer) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.out = w
	isDiscard := int32(0)
	if w == io.Discard {
		isDiscard = 1
	}
	atomic.StoreInt32(&l.isDiscard, isDiscard)
}

var std = New(os.Stderr, "", LstdFlags)

// 默认值返回包级输出函数使用的标准记录器。
func Default() *Logger { return std }

// 廉价整数到固定宽度十进制ASCII。设置负宽度以避免零填充。
func itoa(buf *[]byte, i int, wid int) {
	// 按相反顺序组装十进制数。
	var b [20]byte
	bp := len(b) - 1
	for i >= 10 || wid > 1 {
		wid--
		q := i / 10
		b[bp] = byte('0' + i - q*10)
		bp--
		i = q
	}
	// i<10 
	b[bp] = byte('0' + i)
	*buf = append(*buf, b[bp:]...)
}

// formatHeader按以下顺序将日志头写入buf：
// /*l.prefix（如果它不是空的且Lmsgprefix未设置）、
// /*日期和/或时间（如果提供了相应的标志）、
// /*l.prefix（如果它不是空的并且设置了Lmsgprefix）。
func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) {
	if l.flag&Lmsgprefix == 0 {
		*buf = append(*buf, l.prefix...)
	}
	if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
		if l.flag&LUTC != 0 {
			t = t.UTC()
		}
		if l.flag&Ldate != 0 {
			year, month, day := t.Date()
			itoa(buf, year, 4)
			*buf = append(*buf, '/')
			itoa(buf, int(month), 2)
			*buf = append(*buf, '/')
			itoa(buf, day, 2)
			*buf = append(*buf, ' ')
		}
		if l.flag&(Ltime|Lmicroseconds) != 0 {
			hour, min, sec := t.Clock()
			itoa(buf, hour, 2)
			*buf = append(*buf, ':')
			itoa(buf, min, 2)
			*buf = append(*buf, ':')
			itoa(buf, sec, 2)
			if l.flag&Lmicroseconds != 0 {
				*buf = append(*buf, '.')
				itoa(buf, t.Nanosecond()/1e3, 6)
			}
			*buf = append(*buf, ' ')
		}
	}
	if l.flag&(Lshortfile|Llongfile) != 0 {
		if l.flag&Lshortfile != 0 {
			short := file
			for i := len(file) - 1; i > 0; i-- {
				if file[i] == '/' {
					short = file[i+1:]
					break
				}
			}
			file = short
		}
		*buf = append(*buf, file...)
		*buf = append(*buf, ':')
		itoa(buf, line, -1)
		*buf = append(*buf, ": "...)
	}
	if l.flag&Lmsgprefix != 0 {
		*buf = append(*buf, l.prefix...)
	}
}

// Output写入日志事件的输出。字符串s包含
// 在
// 记录器的标志指定的前缀之后要打印的文本。如果s的最后一个字符不是
// 已经是换行符，则会追加换行符。Calldepth用于恢复PC，是为通用性而提供的
// 尽管目前在所有预定义的
// 路径上是2。
func (l *Logger) Output(calldepth int, s string) error {
	now := time.Now() // 早点来。
	var file string
	var line int
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.flag&(Lshortfile|Llongfile) != 0 {
		// 在获取来电信息时释放锁-这很昂贵。
		l.mu.Unlock()
		var ok bool
		_, file, line, ok = runtime.Caller(calldepth)
		if !ok {
			file = "???"
			line = 0
		}
		l.mu.Lock()
	}
	l.buf = l.buf[:0]
	l.formatHeader(&l.buf, now, file, line)
	l.buf = append(l.buf, s...)
	if len(s) == 0 || s[len(s)-1] != '\n' {
		l.buf = append(l.buf, '\n')
	}
	_, err := l.out.Write(l.buf)
	return err
}

// Printf调用l.Output打印到记录器。
// 参数以fmt的方式处理。Printf。
func (l *Logger) Printf(format string, v ...any) {
	if atomic.LoadInt32(&l.isDiscard) != 0 {
		return
	}
	l.Output(2, fmt.Sprintf(format, v...))
}

// Print调用l.Output打印到记录器。
// 参数以fmt的方式处理。打印
func (l *Logger) Print(v ...any) {
	if atomic.LoadInt32(&l.isDiscard) != 0 {
		return
	}
	l.Output(2, fmt.Sprint(v...))
}

// Println调用l.Output打印到记录器。
// 参数以fmt的方式处理。普林顿。
func (l *Logger) Println(v ...any) {
	if atomic.LoadInt32(&l.isDiscard) != 0 {
		return
	}
	l.Output(2, fmt.Sprintln(v...))
}

// Fatal相当于l.Print（）后跟对os的调用。出口（1）。
func (l *Logger) Fatal(v ...any) {
	l.Output(2, fmt.Sprint(v...))
	os.Exit(1)
}

// Fatalf相当于l.Printf（），后跟对os的调用。出口（1）。
func (l *Logger) Fatalf(format string, v ...any) {
	l.Output(2, fmt.Sprintf(format, v...))
	os.Exit(1)
}

// Fatalln相当于l.Println（）后跟对os的调用。出口（1）。
func (l *Logger) Fatalln(v ...any) {
	l.Output(2, fmt.Sprintln(v...))
	os.Exit(1)
}

// Panic相当于l.Print（）后跟对Panic（）的调用。
func (l *Logger) Panic(v ...any) {
	s := fmt.Sprint(v...)
	l.Output(2, s)
	panic(s)
}

// Panicf相当于l.Printf（）后跟对panic（）的调用。
func (l *Logger) Panicf(format string, v ...any) {
	s := fmt.Sprintf(format, v...)
	l.Output(2, s)
	panic(s)
}

// Panicln相当于l.Println（）后跟对panic（）的调用。
func (l *Logger) Panicln(v ...any) {
	s := fmt.Sprintln(v...)
	l.Output(2, s)
	panic(s)
}

// Flags返回记录器的输出标志。
// 标志位是Ldate、Ltime等。
func (l *Logger) Flags() int {
	l.mu.Lock()
	defer l.mu.Unlock()
	return l.flag
}

// 设置记录器的输出标志。
// 标志位是Ldate、Ltime等。
func (l *Logger) SetFlags(flag int) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.flag = flag
}

// Prefix返回记录器的输出前缀。
func (l *Logger) Prefix() string {
	l.mu.Lock()
	defer l.mu.Unlock()
	return l.prefix
}

// SetPrefix设置记录器的输出前缀。
func (l *Logger) SetPrefix(prefix string) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.prefix = prefix
}

// Writer返回记录器的输出目标。
func (l *Logger) Writer() io.Writer {
	l.mu.Lock()
	defer l.mu.Unlock()
	return l.out
}

// 设置标准记录器的输出目标。
func SetOutput(w io.Writer) {
	std.SetOutput(w)
}

// Flags返回标准记录器的输出标志。
// 标志位是Ldate、Ltime等。
func Flags() int {
	return std.Flags()
}

// 设置标准记录器的输出标志。
// 标志位是Ldate、Ltime等。
func SetFlags(flag int) {
	std.SetFlags(flag)
}

// Prefix返回标准记录器的输出前缀。
func Prefix() string {
	return std.Prefix()
}

// SetPrefix设置标准记录器的输出前缀。
func SetPrefix(prefix string) {
	std.SetPrefix(prefix)
}

// Writer返回标准记录器的输出目标。
func Writer() io.Writer {
	return std.Writer()
}

// 这些函数写入标准记录器。

// Print调用输出以打印到标准记录器。
// 参数以fmt的方式处理。打印
func Print(v ...any) {
	if atomic.LoadInt32(&std.isDiscard) != 0 {
		return
	}
	std.Output(2, fmt.Sprint(v...))
}

// Printf调用输出以打印到标准记录器。
// 参数以fmt的方式处理。Printf。
func Printf(format string, v ...any) {
	if atomic.LoadInt32(&std.isDiscard) != 0 {
		return
	}
	std.Output(2, fmt.Sprintf(format, v...))
}

// Println调用输出以打印到标准记录器。
// 参数以fmt的方式处理。普林顿。
func Println(v ...any) {
	if atomic.LoadInt32(&std.isDiscard) != 0 {
		return
	}
	std.Output(2, fmt.Sprintln(v...))
}

// Fatal相当于Print（）后跟对操作系统的调用。出口（1）。
func Fatal(v ...any) {
	std.Output(2, fmt.Sprint(v...))
	os.Exit(1)
}

// Fatalf相当于Printf（），后跟对os的调用。出口（1）。
func Fatalf(format string, v ...any) {
	std.Output(2, fmt.Sprintf(format, v...))
	os.Exit(1)
}

// Fatalln相当于Println（），后跟对os的调用。出口（1）。
func Fatalln(v ...any) {
	std.Output(2, fmt.Sprintln(v...))
	os.Exit(1)
}

// Panic相当于Print（）后跟对Panic（）的调用。
func Panic(v ...any) {
	s := fmt.Sprint(v...)
	std.Output(2, s)
	panic(s)
}

// Panicf相当于Printf（），后面是对panic（）的调用。
func Panicf(format string, v ...any) {
	s := fmt.Sprintf(format, v...)
	std.Output(2, s)
	panic(s)
}

// Panicln相当于Println（），后面是对panic（）的调用。
func Panicln(v ...any) {
	s := fmt.Sprintln(v...)
	std.Output(2, s)
	panic(s)
}

// Output为日志事件写入输出。字符串s包含
// 在
// 记录器的标志指定的前缀之后要打印的文本。如果s的最后一个字符不是
// 已经是换行符，则会追加换行符。Calldepth是计算文件名和行号
// 帧数（如果设置了Llongfile或Lshortfile）；值为1将为输出的调用者打印详细信息
// 时要跳过的
// 。
func Output(calldepth int, s string) error {
	return std.Output(calldepth+1, s) // /+1用于此框架。
}
